// Calls YYYYMMDDToSystemTime() to fill up to two elements of the aSystemTime array.
// Returns a GDTR bitwise combination to indicate which of the two elements, or both, are valid.
// Caller must ensure that aYYYYMMDD is a modifiable string since it's temporarily altered and restored here.
{
DWORD gdtr = 0;
if (!*aYYYYMMDD)
return gdtr;
if (*aYYYYMMDD != '-') // Since first char isn't a dash, there is a minimum present.
{
char *cp;
if (cp = strchr(aYYYYMMDD + 1, '-'))
*cp = '\0'; // Temporarily terminate in case only the leading part of the YYYYMMDD format is present. Otherwise, the dash and other chars would be considered invalid fields.
if (YYYYMMDDToSystemTime(aYYYYMMDD, aSystemTime[0], true)) // Date string is valid.
gdtr |= GDTR_MIN; // Indicate that minimum is present.
if (cp)
{
*cp = '-'; // Undo the temp. termination.
aYYYYMMDD = cp + 1; // Set it to the maximum's position for use below.
}
else // No dash, so there is no maximum. Indicate this by making aYYYYMMDD empty.
aYYYYMMDD = "";
}
else // *aYYYYMMDD=='-', so only the maximum is present; thus there will be no minimum.
++aYYYYMMDD; // Skip over the dash to set it to the maximum's position.
if (*aYYYYMMDD) // There is a maximum.
{
if (YYYYMMDDToSystemTime(aYYYYMMDD, aSystemTime[1], true)) // Date string is valid.
gdtr |= GDTR_MAX; // Indicate that maximum is present.
// Relies on short circuit boolean order to prevent reading beyond the end of the string:
BOOL is_hex = IS_HEX(aBuf); // BOOL vs. bool might squeeze a little more performance out this frequently-called function.
if (is_hex)
aBuf += 2; // Skip over the 0x prefix.
// Set defaults:
BOOL has_decimal_point = false;
BOOL has_at_least_one_digit = false; // i.e. a string consisting of only "+", "-" or "." is not considered numeric.
char c;
for (;; ++aBuf)
{
c = *aBuf;
if (IS_SPACE_OR_TAB(c))
{
if (*omit_leading_whitespace(aBuf)) // But that space or tab is followed by something other than whitespace.
if (!aAllowImpure) // e.g. "123 456" is not a valid pure number.
return PURE_NOT_NUMERIC;
// else fall through to the bottom logic.
// else since just whitespace at the end, the number qualifies as pure, so fall through to the bottom
// logic (it would already have returned in the loop if it was impure)
break;
}
if (!c) // End of string was encountered.
break; // The number qualifies as pure, so fall through to the logic at the bottom. (It would already have returned elsewhere in the loop if the number is impure).
if (c == '.')
{
if (!aAllowFloat || has_decimal_point || is_hex)
// i.e. if aBuf contains 2 decimal points, it can't be a valid number.
// Note that decimal points are allowed in hexadecimal strings, e.g. 0xFF.EE.
// But since that format doesn't seem to be supported by VC++'s atof() and probably
// related functions, and since it's extremely rare, it seems best not to support it.
return PURE_NOT_NUMERIC;
else
has_decimal_point = true;
}
else
{
if (is_hex ? !isxdigit(c) : (c < '0' || c > '9')) // And since we're here, it's not '.' either.
{
if (aAllowImpure) // Since aStr starts with a number (as verified above), it is considered a number.
else // i.e. the strings "." and "-" are not considered to be numeric by themselves.
return PURE_NOT_NUMERIC;
}
else
{
// As written below, this actually tolerates malformed scientific notation such as numbers
// containing two or more E's (e.g. 1.0e4e+5e-6,). But for performance and due to rarity,
// it seems best not to check for them.
if (toupper(c) != 'E' // v1.0.46.11: Support scientific notation in floating point numbers.
|| !(has_decimal_point && has_at_least_one_digit)) // But it must have a decimal point and at least one digit to the left of the 'E'. This avoids variable names like "1e4" from being seen as sci-notation literals (for backward compatibility). Some callers rely on this check.
return PURE_NOT_NUMERIC;
if (aBuf[1] == '-' || aBuf[1] == '+') // The optional sign is present on the exponent.
++aBuf; // Omit it from further consideration so that the outer loop doesn't see it as an extra/illegal sign.
if (aBuf[1] < '0' || aBuf[1] > '9')
// Even if it is an 'e', ensure what follows it is a valid exponent. Some callers rely
// on this check, such as ones that expect "0.6e" to be non-numeric (for "SetFormat Float")
return PURE_NOT_NUMERIC;
}
}
else // This character is a valid digit or hex-digit.
return PURE_NOT_NUMERIC; // i.e. the strings "+" "-" and "." are not numeric by themselves.
}
void strlcpy(char *aDst, const char *aSrc, size_t aDstSize) // Non-inline because it benches slightly faster that way.
// Caller must ensure that aDstSize is greater than 0.
// Caller must ensure that the entire capacity of aDst is writable, EVEN WHEN it knows that aSrc is much shorter
// than the aDstSize. This is because the call to strncpy (which is used for its superior performance) zero-fills
// any unused portion of aDst.
// Description:
// Same as strncpy() but guarantees null-termination of aDst upon return.
// No more than aDstSize - 1 characters will be copied from aSrc into aDst
// (leaving room for the zero terminator, which is always inserted).
// This function is defined in some Unices but is not standard. But unlike
// other versions, this one uses void for return value for reduced code size
// (since it's called in so many places).
{
// Disabled for performance and reduced code size:
//if (!aDst || !aSrc || !aDstSize) return aDstSize; // aDstSize must not be zero due to the below method.
// It might be worthwhile to have a custom char-copying-loop here someday so that number of characters
// actually copied (not including the zero terminator) can be returned to callers who want it.
--aDstSize; // Convert from size to length (caller has ensured that aDstSize > 0).
strncpy(aDst, aSrc, aDstSize); // NOTE: In spite of its zero-filling, strncpy() benchmarks considerably faster than a custom loop, probably because it uses 32-bit memory operations vs. 8-bit.
aDst[aDstSize] = '\0';
}
int snprintf(char *aBuf, int aBufSize, const char *aFormat, ...)
// aBufSize is an int so that any negative values passed in from caller are not lost.
// aBuf will always be terminated here except when aBufSize is <= zero (in which case the caller should
// already have terminated it). If aBufSize is greater than zero but not large enough to hold the
// entire result, as much of the result as possible is copied and the return value is aBufSize - 1.
// Returns the exact number of characters written, not including the zero terminator. A negative
// number is never returned, even if aBufSize is <= zero (which means there isn't even enough space left
// to write a zero terminator), under the assumption that the caller has already terminated the string
// and thus prefers to have 0 rather than -1 returned in such cases.
// MSDN says (about _snprintf(), and testing shows that it applies to _vsnprintf() too): "This function
// does not guarantee NULL termination, so ensure it is followed by sz[size - 1] = 0".
{
// The following should probably never be changed without a full suite of tests to ensure the
// change doesn't cause the finicky _vsnprintf() to break something.
if (aBufSize < 1 || !aBuf || !aFormat) return 0; // It's called from so many places that the extra checks seem warranted.
va_list ap;
va_start(ap, aFormat);
// Must use _vsnprintf() not _snprintf() because of the way va_list is handled:
int result = _vsnprintf(aBuf, aBufSize, aFormat, ap); // "returns the number of characters written, not including the terminating null character, or a negative value if an output error occurs"
aBuf[aBufSize - 1] = '\0'; // Confirmed through testing: Must terminate at this exact spot because _vsnprintf() doesn't always do it.
// Fix for v1.0.34: If result==aBufSize, must reduce result by 1 to return an accurate result to the
// caller. In other words, if the line above turned the last character into a terminator, one less character
// is now present in aBuf.
if (result == aBufSize)
--result;
return result > -1 ? result : aBufSize - 1; // Never return a negative value. See comment under function definition, above.
}
int snprintfcat(char *aBuf, int aBufSize, const char *aFormat, ...)
// aBufSize is an int so that any negative values passed in from caller are not lost.
// aBuf will always be terminated here except when the amount of space left in the buffer is zero or less.
// (in which case the caller should already have terminated it). If aBufSize is greater than zero but not
// large enough to hold the entire result, as much of the result as possible is copied and the return value
// is space_remaining - 1.
// The caller must have ensured that aBuf and aFormat are non-NULL and that aBuf contains a valid string
// (i.e. that it is null-terminated somewhere within the limits of aBufSize).
// Returns the exact number of characters written, not including the zero terminator. A negative
// number is never returned, even if aBufSize is <= zero (which means there isn't even enough space left
// to write a zero terminator), under the assumption that the caller has already terminated the string
// and thus prefers to have 0 rather than -1 returned in such cases.
{
// The following should probably never be changed without a full suite of tests to ensure the
// change doesn't cause the finicky _vsnprintf() to break something.
size_t length = strlen(aBuf);
int space_remaining = (int)(aBufSize - length); // Must cast to int to avoid loss of negative values.
if (space_remaining < 1) // Can't even terminate it (no room) so just indicate that no characters were copied.
return 0;
aBuf += length; // aBuf is now the spot where the new text will be written.
va_list ap;
va_start(ap, aFormat);
// Must use vsnprintf() not snprintf() because of the way va_list is handled:
int result = _vsnprintf(aBuf, (size_t)space_remaining, aFormat, ap); // "returns the number of characters written, not including the terminating null character, or a negative value if an output error occurs"
aBuf[space_remaining - 1] = '\0'; // Confirmed through testing: Must terminate at this exact spot because _vsnprintf() doesn't always do it.
return result > -1 ? result : space_remaining - 1; // Never return a negative value. See comment under function definition, above.
}
// Not currently used by anything, so commented out to possibly reduce code size:
// Replaces all (or aLimit) occurrences of aOld with aNew in aHaystack.
// On success, it returns the number of replacements done (0 if none). On failure (out of memory), it returns 0
// (and if aDest isn't NULL, it also sets *aDest to NULL on failure).
//
// PARAMETERS:
// - aLimit: Specify UINT_MAX to have no restriction on the number of replacements. Otherwise, specify a number >=0.
// - aSizeLimit: Specify -1 to assume that aHaystack has enough capacity for any mode #1 replacement. Otherwise,
// specify the size limit (in either mode 1 or 2), but it must be >= length of aHaystack (simplifies the code).
// - aDest: If NULL, the function will operate in mode #1. Otherwise, it uses mode #2 (see further below).
// - aHaystackLength: If it isn't NULL, *aHaystackLength must be the length of aHaystack. HOWEVER, *aHaystackLength
// is then changed here to be the length of the result string so that caller can use it to improve performance.
//
// MODE 1 (when aDest==NULL): aHaystack is used as both the source and the destination (sometimes temporary memory
// is used for performance, but it's freed afterward and so transparent to the caller).
// When it passes in -1 for aSizeLimit (the deafult), caller must ensure that aHaystack has enough capacity to hold
// the new/replaced result. When non-NULL, aSizeLimit will be enforced by limiting the number of replacements to
// the available memory (i.e. any remamining replacements are simply not done and that part of haystack is unaltered).
//
// MODE 2 (when aDest!=NULL): If zero replacements are needed, we set *aDest to be aHaystack to indicate that no
// new memory was allocated. Otherwise, we store in *aDest the address of the new memory that holds the result.
// - The caller is responsible for any new memory in *aDest (freeing it, etc.)
// - The initial value of *aDest doesn't matter.
// - The contents of aHaystack isn't altered, not even if aOld_length==aNew_length (some callers rely on this).
//
// v1.0.45: This function was heavily revised to improve performance and flexibility. It has also made
// two other/related StrReplace() functions obsolete. Also, the code has been simplified to avoid doing
// a first pass through haystack to find out exactly how many replacements there are because that step
// nearly doubles the time required for the entire operation (in most cases). Its benefit was mainly in
// memory savings and avoidance of any reallocs since the initial alloc was always exactly right; however,
// testing shows that one or two reallocs are generally much quicker than doing the size-calculation phase
// because extra alloc'ing & memcpy'ing is much faster than an extra search through haystack for all the matches.
// Furthermore, the new approach minimizes reallocs by using smart prediction. Furthermore, the caller shrinks
// the result memory via _expand() to avoid having too much extra/overhang. These optimizations seem to make
// the new approach better than the old one in every way, but especially performance.
{
#define REPLACEMENT_MODE2 aDest // For readability.
// THINGS TO SET NOW IN CASE OF EARLY RETURN OR GOTO:
// Set up the input/output lengths:
size_t haystack_length = aHaystackLength ? *aHaystackLength : strlen(aHaystack); // For performance, use caller's length if it was provided.
size_t length_temp; // Just a placeholder/memory location used by the alias below.
size_t &result_length = aHaystackLength ? *aHaystackLength : length_temp; // Make an alias for convenience and maintainability (if this is an output parameter for our caller, this step takes care that in advance).
// Set up the output buffer:
char *result_temp; // In mode #1, holds temporary memory that is freed before we return.
char *&result = aDest ? *aDest : result_temp; // Make an alias for convenience and maintainability (if aDest is non-NULL, it's an output parameter for our caller, and this step takes care that in advance).
result = NULL; // It's allocated only upon first use to avoid a potentially massive allocation that might
result_length = 0; // be wasted and cause swapping (not to mention that we'll have better ability to estimate the correct total size after the first replacement is discovered).
size_t result_size = 0;
// Variables used by both replacement methods.
char *src, *match_pos;
// END OF INITIAL SETUP.
// From now on, result_length and result should be kept up-to-date because they may have been set up
// as output parameters above.
if (!(*aHaystack && *aOld))
{
// Nothing to do if aHaystack is blank. If aOld is blank, that is not supported because it would be an
// infinite loop. This policy is now largely due to backward compatibility because some other policy
// may have been better.
result = aHaystack; // Return unaltered string to caller in its output paremeter (result is an alias for *aDest).
result_length = haystack_length; // This is an alias for an output parameter, so update it for caller.
return 0; // Report "no replacements".
}
size_t aOld_length = strlen(aOld);
size_t aNew_length = strlen(aNew);
int length_delta = (int)(aNew_length - aOld_length); // Cast to int to avoid loss of unsigned. A negative delta means the replacment substring is smaller than what it's replacing.
if (aSizeLimit != -1) // Caller provided a size *restriction*, so if necessary reduce aLimit to stay within bounds. Compare directly to -1 due to unsigned.
{
int extra_room = (int)(aSizeLimit-1 - haystack_length); // Cast to int to preserve negatives.
if (extra_room < 0) // Caller isn't supposed to call it this way. To avoid having to complicate the
aLimit = 0; // calculations in the else-if below, allow no replacements in this case.
else if (length_delta > 0) // New-str is bigger than old-str. This is checked to avoid going negative or dividing by 0 below. A positive delta means length of new/replacement string is greater than that of what it's replacing.
haystack_portion_length = match_pos - src; // The length of the haystack section between the end of the previous match and the start of the current one.
// Using the required length calculated below, expand/realloc "result" if necessary.
//else omit it altogether; i.e. replace every aOld with the empty string.
// Set up src to be the position where the next iteration will start searching. For consistency with
// the in-place method, overlapping matches are not detected. For example, the replacement
// of all occurrences of ".." with ". ." in "..." would produce ". ..", not ". . .":
src = match_pos + aOld_length; // This has two purposes: 1) Since match_pos is about to be altered by strstr, src serves as a placeholder for use by the next iteration; 2) it's also used further below.
}
if (!replacement_count) // No replacements were done, so optimize by keeping the original (avoids a malloc+memcpy).
{
// The following steps are appropriate for both mode #1 and #2 (for simplicity and maintainability,
// they're all done unconditionally even though mode #1 might not require them all).
result = aHaystack; // Return unaltered string to caller in its output paremeter (result is an alias for *aDest).
result_length = haystack_length; // This is an alias for an output parameter, so update it for caller.
return replacement_count;
// Since no memory was allocated, there's never anything to free.
}
// (Below relies only above having returned when no replacements because it assumes result!=NULL from now on.)
// Otherwise, copy the remaining characters after the last replacement (if any) (fixed for v1.0.25.11).
if (haystack_portion_length = haystack_length - (src - aHaystack)) // This is the remaining part of haystack that need to be copied over as-is.
if (new_result_length >= result_size) // Uses >= to allow room for terminator.
STRREPLACE_REALLOC(new_result_length + 1); // This will return if an alloc error occurs.
memcpy(result + result_length, src, haystack_portion_length); // memcpy() usually benches a little faster than strcpy().
result_length = new_result_length; // Remember that result_length is actually an output for our caller, so even if for no other reason, it must be kept accurate for that.
}
result[result_length] = '\0'; // Must terminate it unconditionally because other sections usually don't do it.
if (!REPLACEMENT_MODE2) // Mode #1.
{
// Since caller didn't provide destination memory, copy the result from our temporary memory (that was used
// for performance) back into the caller's original buf (which has already been confirmed to be large enough).
memcpy(aHaystack, result, result_length + 1); // Include the zero terminator.
free(result); // Free the temp. mem that was used for performance.
}
return replacement_count; // The output parameters have already been populated properly above.
out_of_mem: // This can only happen with the extra-memory method above (which due to its nature can't fall back to the in-place method).
if (result)
{
free(result); // Must be freed in mode #1. In mode #2, it's probably a non-terminated string (not to mention being an incomplete result), so if it ever isn't freed, it should be terminated.
result = NULL; // Indicate failure by setting output param for our caller (this also indicates that the memory was freed).
}
result_length = 0; // Output parameter for caller, though upon failure it shouldn't matter (just for robustness).
return 0;
in_place_method:
// This method is available only to mode #1. It should help performance for short strings such as those from
// ExpandExpression().
// This in-place method is used when the extra-memory method wouldn't be faster enough to be worth its cost
// for the particular strings involved here.
//
// Older comment:
// The below doesn't quite work when doing a simple replacement such as ".." with ". .".
// In the above example, "..." would be changed to ". .." rather than ". . ." as it should be.
// Therefore, use a less efficient, but more accurate method instead. UPDATE: But this method
// can cause an infinite loop if the new string is a superset of the old string, so don't use
// it after all.
//for ( ; ptr = StrReplace(aHaystack, aOld, aNew, aStringCaseSense); ); // Note that this very different from the below.
src = match_pos + aNew_length; // The next search should start at this position when all is adjusted below.
if (length_delta) // This check can greatly improve performance if old and new strings happen to be same length.
{
// Since new string can't fit exactly in place of old string, adjust the target area to
// accept exactly the right length so that the rest of the string stays unaltered:
memmove(src, match_pos + aOld_length
, haystack_length - (match_pos - aHaystack) - aOld_length + 1); // +1 to include zero terminator.
// Above: Calculating length vs. using strlen() makes overall speed of the operation about
// twice as fast for some typical test cases in a 2 MB buffer such as replacing \r\n with \n.
}
memcpy(match_pos, aNew, aNew_length); // Perform the replacement.
// Must keep haystack_length updated as we go, for use with memmove() above:
haystack_length += length_delta; // Note that length_delta will be negative if aNew is shorter than aOld.
}
result_length = haystack_length; // Set for caller (it's an alias for an output parameter).
result = aHaystack; // Not actually needed in this method, so this is just for maintainability.
return replacement_count;
}
int PredictReplacementSize(int aLengthDelta, int aReplacementCount, int aLimit, int aHaystackLength
, int aCurrentLength, int aEndOffsetOfCurrMatch)
// Predict how much size the remainder of a replacement operation will consume, including its actual replacements
// and the parts of haystack that won't need replacement.
// PARAMETERS:
// - aLengthDelta: The estimated or actual difference between the length of the replacement and what it's replacing.
// A negative number means the replacement is smaller, which will cause a shrinking of the result.
// - aReplacementCount: The number of replacements so far, including the one the caller is about to do.
// - aLimit: The *remaining* number of replacements *allowed* (not including the one the caller is about to do).
// - aHaystackLength: The total length of the original haystack/subject string.
// - aCurrentLength: The total length of the new/result string including the one the caller is about to do.
// - aEndOffsetOfCurrMatch: The offset of the char after the last char of the current match. For example, if
// the empty string is the current match and it's found at the beginning of haystack, this value would be 0.
{
// Since realloc() is an expensive operation, especially for huge strings, make an extra
// effort to get a good estimate based on how things have been going so far.
// While this should definitely improve average-case memory-utilization and usually performance
// (by avoiding costly realloc's), this estimate is crude because:
// 1) The length of what is being replaced can vary due to wildcards in pattern, etc.
// 2) The length of what is replacing it can vary due to backreferences. Thus, the delta
// of each replacement is only a guess based on that of the current replacement.
// 3) For code simplicity, the number of upcoming replacements isn't yet known; thus a guess
// is made based on how many there have been so far compared to percentage complete.
int total_delta; // The total increase/decrease in length from the number of predicted additional replacements.
int repl_multiplier = aLengthDelta < 0 ? -1 : 1; // Negative is used to keep additional_replacements_expected conservative even when delta is negative.
if (aLengthDelta == 0) // Avoid all the calculations because it will wind up being zero anyway.
total_delta = 0;
else
{
if (!aHaystackLength // aHaystackLength can be 0 if an empty haystack being replaced by something else. If so, avoid divide-by-zero in the prediction by doing something simpler.
|| !aEndOffsetOfCurrMatch) // i.e. don't the prediction if the current match is the empty string and it was found at the very beginning of Haystack because it would be difficult to be accurate (very rare anyway).
total_delta = repl_multiplier * aLengthDelta; // Due to rarity, just allow room for one extra after the one we're about to do.
else // So the above has ensured that the following won't divide by zero anywhere.
{
// The following doesn't take into account the original value of aStartingOffset passed in
// from the caller because:
// 1) It's pretty rare for it to be greater than 0.
// 2) Even if it is, the prediction will just be too conservative at first, but that's
// pretty harmless; and anyway each successive realloc followed by a match makes the
// prediction more and more accurate in spite of aStartingOffset>0.
// percent_complete must be a double because we need at least 9 digits of precision for cases where
/ (double)aHaystackLength; // percent_complete isn't actually a percentage, but a fraction of 1. e.g. 0.5 rather than 50.
int additional_replacements_expected = percent_complete >= 1.0 ? 0 // It's often 100% complete, in which case there's hardly ever another replacement after this one (the only possibility is to replace the final empty-string in haystack with something).
: (int)(
(aReplacementCount / percent_complete) // This is basically "replacements per percentage point, so far".
* (1 - percent_complete) // This is the percentage of haystack remaining to be scanned (e.g. 0.5 for 50%).
+ 1 * repl_multiplier // Add 1 or -1 to make it more conservative (i.e. go the opposite direction of ceil when repl_multiplier is negative).
);
// additional_replacements_expected is defined as the replacements expected *after* the one the caller
// is about to do.
if (aLimit >= 0 && aLimit < additional_replacements_expected)
{ // A limit is currently in effect and it's less than expected replacements, so cap the expected.
// This helps reduce memory utilization.
additional_replacements_expected = aLimit;
}
else // No limit or additional_replacements_expected is within the limit.
{
// So now things are set up so that there's about a 50/50 chance than no more reallocs
// will be needed. Since recalloc is costly (due to internal memcpy), try to reduce
// the odds of it happening without going overboard on memory utilization.
// Something a lot more complicated could be used in place of the below to improve things
// a little, but it just doesn't seem worth it given the usage patterns expected and
// the actual benefits. Besides, there is some limiting logic further below that will
// cap this if it's unreasonably large:
additional_replacements_expected += (int)(0.20*additional_replacements_expected + 1) // +1 so that there's always at least one extra.
* repl_multiplier; // This keeps the estimate conservative if delta < 0.
}
// The following is the "quality" of the estimate. For example, if this is the very first replacement
// and 1000 more replacements are predicted, there will often be far fewer than 1000 replacements;
// in fact, there could well be zero. So in the below, the quality will range from 1 to 3, where
// It seems best to use whichever of the following is greater in the calculation further below:
int haystack_or_new_length = (aCurrentLength > aHaystackLength) ? aCurrentLength : aHaystackLength;
// The following is a crude sanity limit to avoid going overboard with memory
// utilization in extreme cases such as when a big string has many replacements
// in its first half, but hardly any in its second. It does the following:
// 1) When Haystack-or-current length is huge, this tries to keep the portion of the memory increase
// that's speculative proportionate to that length, which should reduce the chance of swapping
// (at the expense of some performance in cases where it causes another realloc to be required).
// 2) When Haystack-or-current length is relatively small, allow the speculative memory allocation
// to be many times larger than that length because the risk of swapping is low. HOWEVER, TO
// AVOID WASTING MEMORY, the caller should probably call _expand() to shrink the result
// when it detects that far fewer replacements were needed than predicted (this is currently
// done by Var::AcceptNewMem()).
int total_delta_limit = (int)(haystack_or_new_length < 10*1024*1024 ? quality*10*1024*1024
: quality*haystack_or_new_length); // See comment above.
total_delta = additional_replacements_expected
* (aLengthDelta < 0 ? -aLengthDelta : aLengthDelta); // So actually, total_delta will be the absolute value.
if (total_delta > total_delta_limit)
total_delta = total_delta_limit;
total_delta *= repl_multiplier; // Convert back from absolute value.
} // The current match isn't an empty string at the very beginning of haystack.
} // aLengthDelta!=0
// Above is responsible for having set total_delta properly.
int subsequent_length = aHaystackLength - aEndOffsetOfCurrMatch // This is the length of the remaining portion of haystack that might wind up going into the result exactly as-is (adjusted by the below).
+ total_delta; // This is additional_replacements_expected times the expected delta (the length of each replacement minus what it replaces) [can be negative].
if (subsequent_length < 0) // Must not go below zero because that would cause the next line to
subsequent_length = 0; // create an increase that's too small to handle the current replacement.
// Return the sum of the following:
// 1) subsequent_length: The predicted length needed for the remainder of the operation.
// 2) aCurrentLength: The amount we need now, which includes room for the replacement the caller is about to do.
// Note that aCurrentLength can be 0 (such as for an empty string replacement).
return subsequent_length + aCurrentLength + 1; // Caller relies on +1 for the terminator.
}
char *TranslateLFtoCRLF(char *aString)
// Can't use StrReplace() for this because any CRLFs originally present in aString are not changed (i.e. they
// don't become CRCRLF) [there may be other reasons].
// Translates any naked LFs in aString to CRLF. If there are none, the original string is returned.
// Otherwise, the translated version is copied into a malloc'd buffer, which the caller must free
// when it's done with it).
{
UINT naked_LF_count = 0;
size_t length = 0;
char *cp;
for (cp = aString; *cp; ++cp)
{
++length;
if (*cp == '\n' && (cp == aString || cp[-1] != '\r')) // Relies on short-circuit boolean order.
++naked_LF_count;
}
if (!naked_LF_count)
return aString; // The original string is returned, which the caller must check for (vs. new string).
// Allocate the new memory that will become the caller's responsibility:
char *buf = (char *)malloc(length + naked_LF_count + 1); // +1 for zero terminator.
if (!buf)
return NULL;
// Now perform the translation.
char *dp = buf; // Destination.
for (cp = aString; *cp; ++cp)
{
if (*cp == '\n' && (cp == aString || cp[-1] != '\r')) // Relies on short-circuit boolean order.
*dp++ = '\r'; // Insert an extra CR here, then insert the '\n' normally below.
*dp++ = *cp;
}
*dp = '\0'; // Final terminator.
return buf; // Caller must free it when it's done with it.
// Assign the color indicated in aColorName (either a name or a hex RGB value) to both
// aColor and aBrush, deleting any prior handle in aBrush first. If the color cannot
// be determined, it will always be set to CLR_DEFAULT (and aBrush set to NULL to match).
// It will never be set to CLR_NONE.
{
COLORREF color;
if (!*aColorName)
color = CLR_DEFAULT;
else
{
color = ColorNameToBGR(aColorName);
if (color == CLR_NONE) // A matching color name was not found, so assume it's a hex color value.
// It seems strtol() automatically handles the optional leading "0x" if present:
color = rgb_to_bgr(strtol(aColorName, NULL, 16));
// if aColorName does not contain something hex-numeric, black (0x00) will be assumed,
// which seems okay given how rare such a problem would be.
}
if (color != aColor) // It's not already the right color.
{
aColor = color; // Set default. v1.0.44.09: Added this line to fix the inability to change to a previously selected color after having changed to the default color.
if (aBrush) // Free the resources of the old brush.
DeleteObject(aBrush);
if (color == CLR_DEFAULT) // Caller doesn't need brush for CLR_DEFAULT, assuming that's even possible.
aBrush = NULL;
else
if ( !(aBrush = CreateSolidBrush(color)) ) // Failure should be very rare.
aColor = CLR_DEFAULT; // A NULL HBRUSH should always corresponds to CLR_DEFAULT.
}
}
COLORREF ColorNameToBGR(char *aColorName)
// These are the main HTML color names. Returns CLR_NONE if a matching HTML color name can't be found.
// Returns CLR_DEFAULT only if aColorName is the word Default.
{
if (!aColorName || !*aColorName) return CLR_NONE;
if (!stricmp(aColorName, "Black")) return 0x000000; // These colors are all in BGR format, not RGB.
if (!stricmp(aColorName, "Silver")) return 0xC0C0C0;
if (!stricmp(aColorName, "Gray")) return 0x808080;
if (!stricmp(aColorName, "White")) return 0xFFFFFF;
if (!stricmp(aColorName, "Maroon")) return 0x000080;
if (!stricmp(aColorName, "Red")) return 0x0000FF;
if (!stricmp(aColorName, "Purple")) return 0x800080;
if (!stricmp(aColorName, "Fuchsia"))return 0xFF00FF;
if (!stricmp(aColorName, "Green")) return 0x008000;
if (!stricmp(aColorName, "Lime")) return 0x00FF00;
if (!stricmp(aColorName, "Olive")) return 0x008080;
if (!stricmp(aColorName, "Yellow")) return 0x00FFFF;
if (!stricmp(aColorName, "Navy")) return 0x800000;
if (!stricmp(aColorName, "Blue")) return 0xFF0000;
if (!stricmp(aColorName, "Teal")) return 0x808000;
if (!stricmp(aColorName, "Aqua")) return 0xFFFF00;
if (!stricmp(aColorName, "Default"))return CLR_DEFAULT;
return CLR_NONE;
}
POINT CenterWindow(int aWidth, int aHeight)
// Given a the window's width and height, calculates where to position its upper-left corner
// so that it is centered EVEN IF the task bar is on the left side or top side of the window.
// This does not currently handle multi-monitor systems explicitly, since those calculations
// require API functions that don't exist in Win95/NT (and thus would have to be loaded
// dynamically to allow the program to launch). Therefore, windows will likely wind up
// being centered across the total dimensions of all monitors, which usually results in
// half being on one monitor and half in the other. This doesn't seem too terrible and
// might even be what the user wants in some cases (i.e. for really big windows).
{
RECT rect;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0); // Get desktop rect excluding task bar.
// Note that rect.left will NOT be zero if the taskbar is on docked on the left.
// Similarly, rect.top will NOT be zero if the taskbar is on docked at the top of the screen.
// GetEnvironmentVariable() could be called twice, the first time to get the actual size. But that would
// probably perform worse since GetEnvironmentVariable() is a very slow function. In addition, it would
// add code complexity, so it seems best to fetch it into a large buffer then just copy it to dest-var.
if (length) // Probably always true under the conditions in effect for our callers.
memcpy(aBuf, buf, length + 1); // memcpy() usually benches a little faster than strcpy().
else // Failure. The buffer's contents might be undefined in this case.
*aBuf = '\0'; // Caller's buf should always have room for an empty string. So make it empty for maintainability, even if not strictly required by caller.
// Caller must ensure that size of aBuf is REALLY aBufSize (even when it knows aBufSize is more than
// it needs) because the API apparently reads/writes parts of the buffer beyond the string it writes!
// Caller must ensure that aBuf isn't NULL because it doesn't seem worth having a "give me the size only" mode.
// This is because the API might return a size that omits the zero terminator, and the only way to find out for
// sure is probably to actually fetch the data and check if the terminator is present.
{
HKEY hkey;
if (RegOpenKeyEx(aRootKey, aSubkey, 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS)
{
*aBuf = '\0';
return 0;
}
DWORD buf_size = (DWORD)aBufSize; // Caller's value might be a constant memory area, so need a modifiable copy.
LONG result = RegQueryValueEx(hkey, aValueName, NULL, NULL, (LPBYTE)aBuf, &buf_size);
RegCloseKey(hkey);
if (result != ERROR_SUCCESS || !buf_size) // Relies on short-circuit boolean order.
{
*aBuf = '\0'; // MSDN says the contents of the buffer is undefined after the call in some cases, so reset it.
return 0;
}
// Otherwise success and non-empty result. This also means that buf_size is accurate and <= to what we sent in.
// Fix for v1.0.47: ENSURE PROPER STRING TERMINATION. This is suspected to be a source of crashing.
// MSDN: "If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been
// stored with the proper null-terminating characters. Therefore, even if the function returns
// ERROR_SUCCESS, the application should ensure that the string is properly terminated before using
// it; otherwise, it may overwrite a buffer. (Note that REG_MULTI_SZ strings should have two
// null-terminating characters.)"
// MSDN: "If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, this size includes any
// terminating null character or characters."
--buf_size; // Convert to the index of the last character written. This is safe because above already checked that buf_size>0.
if (aBuf[buf_size] == '\0') // It wrote at least one zero terminator (it might write more than one for Unicode, or if it's simply stored with more than one in the registry).
{
while (buf_size && !aBuf[buf_size - 1]) // Scan leftward in case more than one consecutive terminator.
--buf_size;
return buf_size; // There's a tiny chance that this could the wrong length, namely when the string contains binary zero(s) to the left of non-zero characters. But that seems too rare to be worth calling strlen() for.
}
// Otherwise, it didn't write a terminator, so provide one.
++buf_size; // To reflect the fact that we're about to write an extra char.
if (buf_size >= aBufSize) // There's no room for a terminator without truncating the data (very rare). Seems best to indicate failure.
{
*aBuf = '\0';
return 0;
}
// Otherwise, there's room for the terminator.
aBuf[buf_size] = '\0';
return buf_size; // There's a tiny chance that this could the wrong length, namely when the string contains binary zero(s) to the left of non-zero characters. But that seems too rare to be worth calling strlen() for.
// Reason for using VirtualAllocEx(): When sending LVITEM structures to a control in a remote process, the
// structure and its pszText buffer must both be memory inside the remote process rather than in our own.
mem = MyVirtualAllocEx(aHandle, NULL, aSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
}
if (!mem)
CloseHandle(aHandle); // Closes the mapping for Win9x and the process handle for other OSes. Caller should ignore the value of aHandle when return value is NULL.
//else leave the handle open (required for both methods). It's the caller's responsibility to close it.
// Caller should ensure that aUseGDIPlusIfAvailable==false when aIconNumber > 0, since it makes no sense otherwise.
HINSTANCE hinstGDI = NULL;
if (aUseGDIPlusIfAvailable && !(hinstGDI = LoadLibrary("gdiplus"))) // Relies on short-circuit boolean order for performance.
aUseGDIPlusIfAvailable = false; // Override any original "true" value as a signal for the section below.
if (!hbitmap && aImageType > -1 && !aUseGDIPlusIfAvailable)
{
// Since image hasn't yet be loaded and since the file type appears to be one supported by
// LoadImage() [icon/cursor/bitmap], attempt that first. If it fails, fall back to the other
// methods below in case the file's internal contents differ from what the file extension indicates.
int desired_width, desired_height;
if (keep_aspect_ratio) // Load image at its actual size. It will be rescaled to retain aspect ratio later below.
{
desired_width = 0;
desired_height = 0;
}
else
{
desired_width = aWidth;
desired_height = aHeight;
}
// For LoadImage() below:
// LR_CREATEDIBSECTION applies only when aImageType == IMAGE_BITMAP, but seems appropriate in that case.
// Also, if width and height are non-zero, that will determine which icon of a multi-icon .ico file gets
// loaded (though I don't know the exact rules of precedence).
// KNOWN LIMITATIONS/BUGS:
// LoadImage() fails when requesting a size of 1x1 for an image whose orig/actual size is small (e.g. 1x2).
// Unlike CopyImage(), perhaps it detects that division by zero would occur and refuses to do the
// calculation rather than providing more code to do a correct calculation that doesn't divide by zero.
// For example:
// LoadImage() Success:
// Gui, Add, Pic, h2 w2, bitmap 1x2.bmp
// Gui, Add, Pic, h1 w1, bitmap 4x6.bmp
// LoadImage() Failure:
// Gui, Add, Pic, h1 w1, bitmap 1x2.bmp
// LoadImage() also fails on:
// Gui, Add, Pic, h1, bitmap 1x2.bmp
// And then it falls back to GDIplus, which in the particular case above appears to traumatize the
// parent window (or its picture control), because the GUI window hangs (but not the script) after
// doing a FileSelectFolder. For example:
// Gui, Add, Button,, FileSelectFile
// Gui, Add, Pic, h1, bitmap 1x2.bmp ; Causes GUI window to hang after FileSelectFolder (due to LoadImage failing then falling back to GDIplus; i.e. GDIplus is somehow triggering the problem).
// Gui, Show
// return
// ButtonFileSelectFile:
// FileSelectFile, outputvar
// return
if (hbitmap = (HBITMAP)LoadImage(NULL, aFilespec, aImageType, desired_width, desired_height
, LR_LOADFROMFILE | LR_CREATEDIBSECTION))
{
// The above might have loaded an HICON vs. an HBITMAP (it has been confirmed that LoadImage()
// will return an HICON vs. HBITMAP is aImageType is IMAGE_ICON/CURSOR). Note that HICON and
// HCURSOR are identical for most/all Windows API uses. Also note that LoadImage() will load
// an icon as a bitmap if the file contains an icon but IMAGE_BITMAP was passed in (at least
// on Windows XP).
if (!keep_aspect_ratio) // No further resizing is needed.
return hbitmap;
// Otherwise, continue on so that the image can be resized via a second call to LoadImage().
}
// v1.0.40.10: Abort if file doesn't exist so that GDIPlus isn't even attempted. This is done because
// loading GDIPlus apparently disrupts the color palette of certain games, at least old ones that use
// DirectDraw in 256-color depth.
else if (GetFileAttributes(aFilespec) == 0xFFFFFFFF) // For simplicity, we don't check if it's a directory vs. file, since that should be too rare.
return NULL;
// v1.0.43.07: Also abort if caller wanted an HICON (not an HBITMAP), since the other methods below
// can't yield an HICON.
else if (aIconNumber > 0)
{
// UPDATE for v1.0.44: Attempt ExtractIcon in case its some extension that's
// was recognized as an icon container (such as AutoHotkeySC.bin) and thus wasn't handled higher above.